import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.StringArray;
import com.sun.jna.Memory;
import com.sun.jna.ptr.PointerByReference;
import com.sun.jna.ptr.IntByReference;

import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jnaex.*;

public class Main {
    private static final Logger log = LoggerFactory.getLogger("jnaex");

    final static IJnaExLib lib = (IJnaExLib)Native.load("jnaex", IJnaExLib.class);

    public static void main(String[] args) {
        log.info("jna.library.path: {}", System.getProperty("jna.library.path"));
        t_jnaex_callback_with_userdata();
        /*
        t_jnaex_double();
        t_jnaex_chars();
        t_jnaex_sendstring();
        t_jnaex_getstring();
        t_jnaex_sendstrings();
        t_jnaex_getstrings();
        t_jnaex_sendstrarrfast();
        t_jnaex_getstrarrfast();
        t_jnaex_sendstruct();
        t_jnaex_getstruct();
        t_jnaex_updatestruct();
        t_jnaex_countstruct();
        t_jnaex_getstructarr();
        t_jnaex_senddoublearr();
        t_jnaex_getdoublearr();
        t_jnaex_senddoublestruct();
        t_jnaex_getdoublestruct();
        t_jnaex_senddatalist();
        t_jnaex_getdatalist();
        */
    }

    static void t_jnaex_callback_with_userdata() {
        String uuid = UUID.randomUUID().toString().replaceAll("-","");
        JnaExObj jnaexObj = new JnaExObj(uuid);

        log.info("[ t_jnaex_callback_with_userdata ] jnaexObj: {}", uuid, jnaexObj);
        int ret = lib.jnaex_call_with_cb(JnaExObj::onCallback, jnaexObj.session, jnaexObj.getPointer());
        log.info("[ t_jnaex_callback_with_userdata ] ret: {}", ret);
    }

    static void t_jnaex_double() {
        int val = 5;
        log.info("[ t_jnaex_double ] val: {}, ret: {}", val, lib.jnaex_double(val));
    }

    static void t_jnaex_chars() {
        final JnaExChars.ByValue chars = lib.jnaex_getchars();
        log.info("[ t_jnaex_chars ] data: {}", chars.data);

        lib.jnaex_chars_free(chars);
    }

    static void t_jnaex_sendstring() {
        lib.jnaex_sendstring("Hello, jna");
    }

    static void t_jnaex_getstring() {
        final PointerByReference ptRef = new PointerByReference();
        lib.jnaex_getstring(ptRef);
        final Pointer pt = ptRef.getValue();
        log.info("[ t_jnaex_getstring ] ret: {}", pt.getString(0));

        lib.jnaex_free(pt);
    }

    static void t_jnaex_sendstrings() {
        final String[] strs = new String[] { "one", "two", "three", "four", "five" };
        JnaExStrings.ByReference jstrRef = new JnaExStrings.ByReference();
        jstrRef.n = strs.length;
        jstrRef.data = new StringArray(strs);

        lib.jnaex_sendstrings(jstrRef);
    }

    static void t_jnaex_getstrings() {
        final JnaExStrings.ByValue ptVal = lib.jnaex_getstrings();
        final String[] strs = ptVal.data.getStringArray(0, ptVal.n);

        for (String s: strs) {
            log.info("[ t_jnaex_getstrings ] s: {}", s);
        }

        lib.jnaex_cleanstrs(ptVal);
    }

    static void t_jnaex_sendstrarrfast() {
        final String[] strs = new String[] { "one", "two", "three", "four", "five" };
        final int len = 24;

        Pointer ptStrArr = new Memory(len * Native.getNativeSize(Character.TYPE));
        long offset = 0;
        for (String s : strs) {
            ptStrArr.setString(offset, s);
            ptStrArr.setMemory(offset + s.length(), 1, (byte)(0));
            offset += s.length() + 1;
        }

        lib.jnaex_sendstrarrfast(ptStrArr, strs.length);
    }

    static void t_jnaex_getstrarrfast() {
        final PointerByReference ptStrArrRef = new PointerByReference();
        final IntByReference ptN = new IntByReference();

        lib.jnaex_getstrarrfast(ptStrArrRef, ptN);

        int n = ptN.getValue();
        final Pointer ptStrArr = ptStrArrRef.getValue();
        long offset = 0;
        int idx = 0;

        while (idx < n) {
            String s = ptStrArr.getString(offset);
            log.info("[ t_jnaex_getstrarrfast ] idx: {}, offset: {}, s: {}", idx, offset, s);

            offset += s.length() + 1;
            idx++;
        }

        lib.jnaex_free(ptStrArr);
    }

    static void t_jnaex_sendstruct() {
        final JnaExData.ByReference data = new JnaExData.ByReference();
        data.val = 6;
        lib.jnaex_sendstruct(data);
    }

    static void t_jnaex_getstruct() {
        final JnaExData.ByValue data = lib.jnaex_getstruct();
        log.info("[ t_jnaex_getstruct ] val: {}", data.val);
    }

    static void t_jnaex_updatestruct() {
        final JnaExData.ByReference data = new JnaExData.ByReference();
        data.val = 6;
        log.info("[ t_jnaex_updatestruct.before ] val: {}", data.val);
        lib.jnaex_updatestruct(data);
        log.info("[ t_jnaex_updatestruct.after ] val: {}", data.val);
    }

    static void t_jnaex_countstruct() {
        int size = 5;
        int expect = 0;
        final JnaExData.ByReference data = new JnaExData.ByReference();
        final JnaExData[] arr = (JnaExData[])data.toArray(size);

        for (int i = 0; i < size; i++) {
           int val = (i + 1) * 10;
           arr[i].val = val;
           expect += val;
        }

        int total = lib.jnaex_countstruct(data, size);
        log.info("[ t_jnaex_countstruct ] total: {}, expected: {}", total, expect);
    }

    static void t_jnaex_getstructarr() {
        final PointerByReference ptrDataRef = new PointerByReference();
        final IntByReference ptrSize = new IntByReference();

        lib.jnaex_getstructarr(ptrDataRef, ptrSize);

        int size = ptrSize.getValue();
        final Pointer ptrDataVal = ptrDataRef.getValue();
        final JnaExData dataRef = new JnaExData(ptrDataVal);
        dataRef.read();
        final JnaExData[] arr = (JnaExData[])dataRef.toArray(size);

        for (int i = 0; i < size; i++) {
            JnaExData item = arr[i];
            log.info("[ t_jnaex_getstructarr ] : index: {}, val: {}", i, item.val);
        }

        lib.jnaex_free(ptrDataVal);
    }

    static void t_jnaex_senddoublearr() {
        final Pointer ptr = new Memory(100 * Native.getNativeSize(Double.TYPE));
        for (int idx = 0; idx < 100; idx++) {
            ptr.setDouble(idx * Native.getNativeSize(Double.TYPE), ((double)idx + 100) / 100);
        }

        double ret = lib.jnaex_senddoublearr(ptr, 100);
        log.info("[ t_jnaex_senddoublearr ] : ret: {}", ret);
    }

    static void t_jnaex_getdoublearr() {
        final PointerByReference ptrRef = new PointerByReference();
        final IntByReference ptrLen = new IntByReference();

        lib.jnaex_getdoublearr(ptrRef, ptrLen);

        int len = ptrLen.getValue();
        log.info("[ t_jnaex_getdoublearr ] : length: {}", len);

        if (len > 0) {
            final Pointer ptrVals = ptrRef.getValue();
            double total = 0.0;

            for (int idx = 0; idx < len; idx++) {
                double val = ptrVals.getDouble(idx * Native.getNativeSize(Double.TYPE));
                total += val;
            }

            log.info("[ t_jnaex_getdoublearr ] : total: {}", total);

            lib.jnaex_free(ptrVals);
        }
    }

    static void t_jnaex_senddoublestruct() {
        final JnaExDouble.ByReference ds = new JnaExDouble.ByReference();
        ds.n = 100;
        ds.data = new Memory(ds.n * Native.getNativeSize(Double.TYPE));
        for (int idx = 0; idx < ds.n; idx++) {
            ds.data.setDouble(idx * Native.getNativeSize(Double.TYPE), ((double)idx + 100) / 100);
        }

        double ret = lib.jnaex_senddoublestruct(ds);
        log.info("[ t_jnaex_senddoublestruct ] : ret: {}", ret);
    }

    static void t_jnaex_getdoublestruct() {
        final JnaExDouble.ByValue dsv = lib.jnaex_getdoublestruct();
        double total = 0.0;

        if (dsv.n > 0) {
            for (int idx = 0; idx < dsv.n; idx++) {
                total += dsv.data.getDouble(idx * Native.getNativeSize(Double.TYPE));
            }

            lib.jnaex_free(dsv.data);
        }

        log.info("[ t_jnaex_getdoublestruct ] n: {}, total: {}", dsv.n, total);
    }

    static void t_jnaex_senddatalist() {
        final JnaExDataList.ByReference datalist = new JnaExDataList.ByReference();
        datalist.n = 5;
        datalist.data = new JnaExData.ByReference();
        final JnaExData[] items = (JnaExData[])datalist.data.toArray(datalist.n);

        for (int idx = 0; idx < datalist.n; idx++) {
            items[idx].val = idx * idx;
        }

        lib.jnaex_senddatalist(datalist);
    }

    static void t_jnaex_getdatalist() {
        final JnaExDataList datalist = lib.jnaex_getdatalist();
        log.info("[ t_jnaex_getdatalist ] {}", datalist);

        final JnaExData[] data = (JnaExData[])datalist.data.toArray(datalist.n);

        for (int idx = 0; idx < datalist.n; idx++) {
            JnaExData item = data[idx];
            log.info("[ t_jnaex_getdatalist ] idx: {}, val: {}, str: {}", idx, item.val, item.str);
        }

        //lib.jnaex_data_list_free(datalist);
    }
}
